上一篇文章初體驗 Plugin 的具體功用,範例中使用了 C# 程式語言來撰寫處理日期與時間的 Plugin,並透過 Semantic Kernel 與 LLMs 發揮其作用。然而,在設計 Plugin 的 functions 時,經常會需要傳入參數,才能進行邏輯處理。舉例來說,若要取得某個人的 Email,此時的 function 可能需要傳入相關的識別資訊,如使用者名稱或 ID,來完成該操作。
public string GetEmail(string name)
{
//......
}
這段程式碼對於開發人員來說再熟悉不過了,傳統上我們是透過程式碼來呼叫 function,因此可以很直覺的傳入所需的參數值。但是,隨著 LLMs(大型語言模型)的應用發展,function 的呼叫不再僅限於傳統的程式呼叫方式,也可以自動根據對話進行呼叫(如我們在上一篇文章中所體驗到的)。因此,問題來了:參數值應該如何提供?又或者說,在 Semantic Kernel 框架中,如何將對話中的內容轉換成 Plugin function 所需的參數值?
舉個例子,假設我們正在設計一款旅行規劃的生成式應用,這款應用需要一個 Plugin 來提供一個對應的 function,該 function 的作用是根據指定的城市提供天氣資料,進而為旅行規劃提供建議。在這種情況下,除了在 function 本身加上 Description 屬性來說明其用途之外,連同參數也需要加上 Description 屬性。這樣做的目的是讓 LLMs 能夠理解 function 及其參數的具體用途。
這一點至關重要,因為 Description 的品質直接影響了 LLMs 在實際應用中的表現。精確、清晰的描述有助於 LLMs 正確解析語義,進而可以準確傳遞對話中的資訊,提升 function 的運作效果。
public class TravelPlugin
{
private readonly Dictionary<string, string> _weatherData = new Dictionary<string, string>
{
{ "台北", "悶熱, 35°C" },
{ "台中", "晴朗, 32°C" },
{ "高雄", "炎熱, 35°C" },
{ "台南", "晴朗, 34°C" },
{ "台東", "陰雨, 28°C" }
};
[KernelFunction, Description("Retrieves the weather information for a specific city.")]
public string GetWeather([Description("specific city name")] string city)
{
if (_weatherData.TryGetValue(city, out var weather))
{
return weather;
}
else
{
return "Weather data not available for the specified city.";
}
}
}
接著建立 Kernel 物件掛載 Plugin ,並使用 AutoInvokeKernelFunctions 方式,來測試是否可以達到預期效果。
var builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
endpoint: Config.aoai_endpoint,
deploymentName: Config.aoai_deployment,
apiKey: Config.aoai_apiKey);
builder.Plugins.AddFromType<TravelPlugin>();
Kernel kernel = builder.Build();
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
Console.WriteLine(await kernel.InvokePromptAsync("高雄天氣如何", new(settings)));
執行結果:
高雄目前的天氣炎熱,氣溫為35°C。請注意防曬和保持水分!
從結果來看,function 所需的 city 參數值被從對話文字中擷取(這部份依賴的是 Semantic Kernel 與 LLMs 模型),並且順利呼叫 function 取回天氣資料。
那麼如果是採取傳統方式於程式流程中指定呼叫 Plugin 的 function 時,又該如何把參數值傳入 function ?
— 使用 plugin name 由 kernel 物件取得 plugin
— 呼叫 kernel.InvokeAsync 方法,指定 functon name 來呼叫該plugin 裡的特定 function
— function 參數值,則透過 kernel.InvokeAsync 的 KernelArguments 傳入
var plugin = kernel.Plugins["TravelPlugin"];
var reult = await kernel.InvokeAsync(plugin["GetWeather"], new() { ["city"] = "台北" });
Console.WriteLine(reult.GetValue<string>());
執行結果:
悶熱, 35°C
可以發現到,這時候得的結果並不會有生成式AI的效果,其回應內容僅僅就是 functon 的固定回傳值而已。
從上一篇到本篇,範例都運用了 AutoInvokeKernelFunctions。因此,讀者們可能會產生一個疑問:在使用 Plugin 的 functions 時,應該選擇哪一種方式來呼叫?其實,這兩種寫法應根據具體的需求與情境來決定。
AutoInvokeKernelFunctions
如果應用情境是希望系統能根據對話內容自動決定何時執行某個 function,那麼 AutoInvokeKernelFunctions 是理想的選擇。它可以透過語意解析,從對話中自動判斷並執行對應的 function,適合需要高度互動性或自動化的應用場景。這時候設計Plugin Function時,Attribute 就很重要了,它決定了 AutoInvokeKernelFunctions 是否能夠準確運作。此外,何時調用 function 以及參數值的傳入是否正確,完全由 Semantic Kernel 和 LLMs 模型控制,這其中存在一定的不確定性。然而,function 的回應內容往往具備變化性,能夠呈現生成式效果,這也是 LLMs 的一大優勢。相較於傳統的流程控制(如 if..else..),這種方式取代了過去嚴格的規則式控制,轉而依賴模型的語義理解和生成能力,讓系統更加靈活和自動化。
傳統的程式流程呼叫
使用傳統的流程規則控制(if..else..),擁有完全的控制權相對缺少彈性,參數的傳入同樣由程式邏輯控制,適合需要更明確控制或需要開發者自行管理 function 執行時機的情況。這種方式適用於需要精確參數傳遞和更嚴格邏輯控制的場景,相對的,Function 的回應內容不具有生成式效果。
因此,兩者的選擇應取決於應用的設計需求以及對自動化與控制權的平衡。